package com.ceair.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ceair.api.SystemFeignClient;
import com.ceair.entity.ActReProcdef;
import com.ceair.entity.model.UserInfo;
import com.ceair.entity.request.*;
import com.ceair.entity.result.Result;
import com.ceair.entity.vo.DynamicParametersVO;
import com.ceair.entity.vo.Oauth2BasicUserVO;
import com.ceair.entity.vo.SelectDataVO;
import com.ceair.entity.vo.SysRoleVO;
import com.ceair.enums.SuspensionState;
import com.ceair.exception.BusinessException;
import com.ceair.mapper.ActReProcdefMapper;
import com.ceair.service.IActReProcdefService;
import com.ceair.util.UserInfoUtils;
import io.micrometer.common.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author wangbaohai
 * @since 2025-04-20
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ActReProcdefServiceImpl extends ServiceImpl<ActReProcdefMapper, ActReProcdef> implements IActReProcdefService {

    private final RepositoryService repositoryService;
    private final SystemFeignClient systemFeignClient;
    private final RuntimeService runtimeService;
    private final IdentityService identityService;
    // 用户工具
    private final UserInfoUtils userInfoUtils;

    /**
     * 分页查询流程定义基础信息。
     *
     * @param queryActReProcdefReq 查询请求对象，包含分页参数和查询条件。
     *                             如果为 null 或其分页参数为空，则使用默认分页参数（第一页，每页10条数据）。
     * @return 返回一个分页对象 Page<ActReProcdef>，包含查询结果和分页信息。
     * 如果查询过程中发生异常，则抛出 BusinessException。
     */
    @Override
    public Page<ActReProcdef> queryActReProcdefWithPage(QueryActReProcdefReq queryActReProcdefReq) {
        // 初始化分页工具以及参数，默认为第一页，每页10条数据
        long current = 1;
        long size = 10;

        // 检查请求对象及其分页参数是否为空，并根据需要设置分页参数
        if (queryActReProcdefReq != null && queryActReProcdefReq.getPageReq() != null) {
            current = queryActReProcdefReq.getPageReq().getCurrent() > 0 ?
                    queryActReProcdefReq.getPageReq().getCurrent() : current;
            size = queryActReProcdefReq.getPageReq().getSize() > 0 ? queryActReProcdefReq.getPageReq().getSize() : size;
        }

        // 初始化分页对象，用于存储分页查询结果
        Page<ActReProcdef> page = new Page<>(current, size);

        // 调用 mapper 进行分页查询，捕获可能的异常并记录日志
        try {
            return this.baseMapper.queryActReProcdefWithPage(page, queryActReProcdefReq);
        } catch (Exception e) {
            // 捕获异常并记录日志，避免系统崩溃
            log.error("查询流流程定义基础信息分页失败，请求参数: {}", queryActReProcdefReq, e);
            throw new BusinessException("查询流程定义基础信息分页失败，请稍后重试", e);
        }
    }

    /**
     * 激活或挂起指定的流程定义。
     * <p>
     * 该方法根据请求对象中的流程定义ID和操作类型，执行激活或挂起流程定义的操作。
     * 支持级联操作相关流程实例，并可设置操作时间（立即或指定时间）。
     * <p>
     * 参数说明：
     * - operateActReProcdefReq: 请求对象，包含流程定义ID和操作类型。
     * 如果为 null 或其字段不符合要求，则会抛出异常。
     * <p>
     * 返回值：
     * - Boolean: 操作成功时返回 true。如果发生异常，则不会返回值，而是抛出 BusinessException。
     * <p>
     * 异常说明：
     * - BusinessException: 当参数校验失败、操作类型非法或发生其他异常时抛出。
     */
    @Override
    public Boolean operateProcessDefinitionById(OperateActReProcdefReq operateActReProcdefReq) {
        try {
            // 参数校验
            if (operateActReProcdefReq == null) {
                log.error("激活或者挂起流程失败，原因：请求对象不能为空");
                throw new BusinessException("激活或者挂起流程失败，原因：请求对象不能为空");
            }

            String processDefinitionId = operateActReProcdefReq.getProcessDefinitionId();
            Integer operateType = operateActReProcdefReq.getOperateType();

            if (processDefinitionId == null || processDefinitionId.trim().isEmpty()) {
                log.error("激活或者挂起流程失败，原因：流程定义ID不能为空或空字符串");
                throw new BusinessException("激活或者挂起流程失败，原因：流程定义ID不能为空或空字符串");
            }

            if (operateType == null) {
                log.error("激活或者挂起流程失败，原因：操作类型不能为空");
                throw new BusinessException("激活或者挂起流程失败，原因：操作类型不能为空");
            }

            // 操作类型校验
            if (!Objects.equals(SuspensionState.ACTIVE.getCode(), operateType) && !Objects.equals(SuspensionState.SUSPENDED.getCode(), operateType)) {
                log.error("激活或者挂起流程失败，原因：操作类型错误，非法值为：{}", operateType);
                throw new BusinessException("激活或者挂起流程失败，原因：操作类型错误");
            }

            // 根据操作类型执行相应逻辑
            if (Objects.equals(SuspensionState.ACTIVE.getCode(), operateType)) {
                log.info("尝试激活流程定义，流程定义ID：{}", processDefinitionId);
                /*
                 * 激活指定的流程定义。
                 *
                 * 该方法通过调用 repositoryService 的 activateProcessDefinitionById 方法，
                 * 激活与给定流程定义 ID 对应的流程定义。支持级联激活相关实例，并可设置激活时间。
                 *
                 * 参数说明：
                 * - operateActReProcdefReq.getProcessDefinitionId(): 流程定义的唯一标识符，用于定位需要激活的流程定义。
                 *                                                    如果为空，则无法定位目标流程定义，可能导致异常。
                 * - true: 表示是否级联激活相关流程实例。如果为 true，则所有关联的流程实例也会被激活；
                 *         如果为 false，则仅激活流程定义本身。
                 * - null: 表示激活时间。如果为 null，则立即激活；如果提供具体时间，则会在指定时间激活。
                 */
                repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
            } else if (Objects.equals(SuspensionState.SUSPENDED.getCode(), operateType)) {
                log.info("尝试挂起流程定义，流程定义ID：{}", processDefinitionId);
                /*
                 * 暂停指定的流程定义。
                 *
                 * 该方法通过调用 repositoryService 的 suspendProcessDefinitionById 方法，
                 * 暂停与给定流程定义 ID 对应的流程定义。支持级联暂停相关实例，并可设置暂停时间。
                 *
                 * 参数说明：
                 * - operateActReProcdefReq.getProcessDefinitionId(): 流程定义的唯一标识符，用于定位需要暂停的流程定义。
                 *                                                    如果为空，则无法定位目标流程定义，可能导致异常。
                 * - true: 表示是否级联暂停相关流程实例。如果为 true，则所有关联的流程实例也会被暂停；
                 *         如果为 false，则仅暂停流程定义本身。
                 * - null: 表示暂停时间。如果为 null，则立即暂停；如果提供具体时间，则会在指定时间暂停。
                 */
                repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
            }
        } catch (IllegalArgumentException e) {
            if (operateActReProcdefReq != null) {
                log.error("激活或者挂起流程失败，原因：非法参数，流程定义ID：{}, 操作类型：{}", operateActReProcdefReq.getProcessDefinitionId(),
                        operateActReProcdefReq.getOperateType(), e);
            }
            throw new BusinessException("激活或者挂起流程失败，原因：非法参数", e);
        } catch (Exception e) {
            if (operateActReProcdefReq != null) {
                log.error("激活或者挂起流程失败，原因：未知异常，流程定义ID：{}, 操作类型：{}", operateActReProcdefReq.getProcessDefinitionId(),
                        operateActReProcdefReq.getOperateType(), e);
            }
            throw new BusinessException("激活或者挂起流程失败，原因：未知异常", e);
        }

        return true;
    }

    /**
     * 查询流程图的Base64编码字符串。
     *
     * @param queryImageOrXmlReq 请求对象，包含查询流程图所需的参数。
     *                           - 不能为空。
     *                           - 必须包含有效的流程定义ID（processDefinitionId）。
     * @return 返回流程图的Base64编码字符串。
     * - 如果流程定义不存在或资源流为空，则抛出异常。
     * @throws IllegalArgumentException 如果请求对象为空或流程定义ID无效。
     * @throws BusinessException        如果流程定义不存在、资源流为空或发生IO异常。
     */
    @Override
    public String queryImage(QueryImageOrXmlReq queryImageOrXmlReq) {
        // 参数校验：确保请求对象不为空
        if (queryImageOrXmlReq == null) {
            log.error("获取流程图失败，原因：请求对象不能为空");
            throw new IllegalArgumentException("获取流程图失败，原因：请求对象不能为空");
        }

        String processDefinitionId = queryImageOrXmlReq.getProcessDefinitionId();

        // 参数校验：确保流程定义ID不为空或空字符串
        if (processDefinitionId == null || processDefinitionId.trim().isEmpty()) {
            log.error("获取流程图失败，原因：流程定义ID不能为空或空字符串");
            throw new IllegalArgumentException("获取流程图失败，原因：流程定义ID不能为空或空字符串");
        }

        // 根据流程定义ID查询流程定义
        ActReProcdef actReProcdef = getById(processDefinitionId);
        if (actReProcdef == null) {
            log.error("获取流程图失败，原因：流程定义不存在，流程定义ID：{}", processDefinitionId);
            throw new BusinessException("获取流程图失败，原因：流程定义不存在，流程定义ID：" + processDefinitionId);
        }

        // 获取流程图资源流
        try (InputStream imageStream = repositoryService.getResourceAsStream(actReProcdef.getDeploymentId(),
                actReProcdef.getDgrmResourceName())) {
            if (imageStream == null) {
                log.error("获取流程图失败，原因：资源流为空，流程定义ID：{}", processDefinitionId);
                throw new BusinessException("获取流程图失败，原因：资源流为空，流程定义ID：" + processDefinitionId);
            }

            // 将资源流内容读取为字节数组并进行Base64编码
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = imageStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return Base64.getEncoder().encodeToString(outputStream.toByteArray());
        } catch (IOException e) {
            // 捕获IO异常并记录日志
            log.error("获取流程图失败，原因：IO异常，流程定义ID：{}", processDefinitionId, e);
            throw new BusinessException("获取流程图失败，原因：IO异常", e);
        } catch (Exception e) {
            // 捕获其他未知异常并记录日志
            log.error("获取流程图失败，原因：未知异常，流程定义ID：{}", processDefinitionId, e);
            throw new BusinessException("获取流程图失败，原因：未知异常", e);
        }
    }

    /**
     * 根据流程定义ID查询流程XML信息
     *
     * @param queryImageOrXmlReq 查询请求对象，包含流程定义ID
     * @return 返回流程XML的Base64编码字符串
     * @throws IllegalArgumentException 如果请求对象或流程定义ID为空
     * @throws BusinessException        如果流程定义不存在或读取资源流时发生错误
     */
    @Override
    public String queryXML(QueryImageOrXmlReq queryImageOrXmlReq) {
        // 参数校验：确保请求对象不为空
        if (queryImageOrXmlReq == null) {
            log.error("获取流程XML失败，原因：请求对象不能为空");
            throw new IllegalArgumentException("获取流程XML失败，原因：请求对象不能为空");
        }

        String processDefinitionId = queryImageOrXmlReq.getProcessDefinitionId();

        // 参数校验：确保流程定义ID不为空或空字符串
        if (processDefinitionId == null || processDefinitionId.trim().isEmpty()) {
            log.error("获取流程XML失败，原因：流程定义ID不能为空或空字符串");
            throw new IllegalArgumentException("获取流程XML失败，原因：流程定义ID不能为空或空字符串");
        }

        // 根据流程定义ID查询流程定义
        ActReProcdef actReProcdef = getById(processDefinitionId);
        if (actReProcdef == null) {
            log.error("获取流程XML失败，原因：流程定义不存在，流程定义ID：{}", processDefinitionId);
            throw new BusinessException("获取流程XML失败，原因：流程定义不存在，流程定义ID：" + processDefinitionId);
        }

        // 获取流程图资源流
        try (InputStream xmlStream = repositoryService.getResourceAsStream(actReProcdef.getDeploymentId(),
                actReProcdef.getResourceName())) {
            if (xmlStream == null) {
                log.error("获取流程XML失败，原因：资源流为空，流程定义ID：{}", processDefinitionId);
                throw new BusinessException("获取流程XML失败，原因：资源流为空，流程定义ID：" + processDefinitionId);
            }

            // 将资源流内容读取为字节数组并进行Base64编码
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = xmlStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }

            // 将 outputStream 使用 urlEncoder 进行编码
            return URLEncoder.encode(Base64.getEncoder().encodeToString(outputStream.toByteArray()),
                    StandardCharsets.UTF_8);
        } catch (IOException e) {
            // 捕获IO异常并记录日志
            log.error("获取流程XML失败，原因：IO异常，流程定义ID：{}", processDefinitionId, e);
            throw new BusinessException("获取流程XML失败，原因：IO异常", e);
        } catch (Exception e) {
            // 捕获其他未知异常并记录日志
            log.error("获取流程XML失败，原因：未知异常，流程定义ID：{}", processDefinitionId, e);
            throw new BusinessException("获取流程XML失败，原因：未知异常", e);
        }
    }

    /**
     * 查询流程动态参数。
     * <p>
     * 该方法根据指定的流程定义ID，查询并生成与用户任务相关的动态参数表单数据，供前端使用。
     * 动态参数表单包括用户和角色的选择数据，用于在流程中分配任务。
     * <p>
     * 参数说明：
     *
     * @param queryDynamicParametersReq 请求对象，包含流程定义ID（processDefinitionId）。
     *                                  - 不可为空。
     *                                  - processDefinitionId 必须为有效的非空字符串。
     *                                  <p>
     *                                  返回值：
     * @return 返回一个 DynamicParametersVO 对象列表，包含所有用户任务的动态参数表单数据。
     * 如果发生异常，则不会返回值，而是抛出相应的异常。
     * <p>
     * 异常说明：
     * - IllegalArgumentException: 当请求对象或流程定义ID无效时抛出。
     * - BusinessException: 当业务逻辑出现问题（如远程调用失败、流程定义元素为空等）时抛出。
     * - Exception: 捕获其他未知异常，并将其包装为 BusinessException 抛出。
     */
    @Override
    public List<DynamicParametersVO> queryDynamicParameters(QueryDynamicParametersReq queryDynamicParametersReq) {
        try {
            // 初始化动态参数列表
            List<DynamicParametersVO> dynamicParametersVOList = new ArrayList<>();

            // 参数校验：确保请求对象不为空
            if (queryDynamicParametersReq == null) {
                log.error("获取流程动态参数失败，原因：请求对象不能为空");
                throw new IllegalArgumentException("获取流程动态参数失败，原因：请求对象不能为空");
            }

            String processDefinitionId = queryDynamicParametersReq.getProcessDefinitionId();

            // 参数校验：确保流程定义ID不为空或空字符串
            if (StringUtils.isBlank(processDefinitionId)) {
                log.error("获取流程动态参数失败，原因：流程定义ID不能为空或空字符串，流程定义ID：{}", processDefinitionId);
                throw new IllegalArgumentException("获取流程动态参数失败，原因：流程定义ID不能为空或空字符串");
            }

            // 根据流程定义ID查询流程定义元素
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

            // 调用远程接口获取所有用户信息
            Result<List<Oauth2BasicUserVO>> userResult = systemFeignClient.queryAllUsers();
            if (!userResult.getSuccess() || userResult.getData() == null) {
                log.error("获取流程动态参数失败，原因：获取所有用户信息失败，失败原因：{}", userResult.getMessage());
                throw new BusinessException("获取流程动态参数失败，原因：获取所有用户信息失败，失败原因：" + userResult.getMessage());
            }
            List<SelectDataVO> userSelectDataList = convertToSelectDataVOList(userResult.getData());

            // 调用远程接口获取所有角色信息
            Result<List<SysRoleVO>> roleResult = systemFeignClient.queryAllRoles();
            if (!roleResult.getSuccess() || roleResult.getData() == null) {
                log.error("获取流程动态参数失败，原因：获取所有角色信息失败，失败原因：{}", roleResult.getMessage());
                throw new BusinessException("获取流程动态参数失败，原因：获取所有角色信息失败，失败原因：" + roleResult.getMessage());
            }
            List<SelectDataVO> roleSelectDataList = convertToSelectDataVOList(roleResult.getData());

            // 从 bpmnModel 获取主流程元素
            Process process = bpmnModel.getMainProcess();
            if (process == null) {
                log.error("获取流程动态参数失败，原因：流程定义元素为空，流程定义ID：{}", processDefinitionId);
                throw new BusinessException("获取流程动态参数失败，原因：流程定义元素为空，流程定义ID：" + processDefinitionId);
            }

            // 获取流程中的所有 FlowElement 元素
            Collection<FlowElement> flowElements = process.getFlowElements();

            // 处理所有用户任务，生成动态参数表单数据
            flowElements.stream().filter(Objects::nonNull)
                    .filter(flowElement -> flowElement instanceof UserTask).map(UserTask.class::cast)
                    .forEach(userTask -> processUserTask(userTask, userSelectDataList, roleSelectDataList,
                            dynamicParametersVOList));

            // 返回动态参数表单数据
            return dynamicParametersVOList;
        } catch (IllegalArgumentException e) {
            log.error("获取流程动态参数失败，原因：参数错误", e);
            throw new BusinessException("获取流程动态参数失败，原因：参数错误", e);
        } catch (BusinessException e) {
            log.error("获取流程动态参数失败，原因：业务异常", e);
            throw new BusinessException("获取流程动态参数失败，原因：业务异常", e);
        } catch (Exception e) {
            log.error("获取流程动态参数失败，原因：未知异常", e);
            throw new BusinessException("获取流程动态参数失败，原因：未知异常", e);
        }
    }

    /**
     * 启动流程实例的方法
     *
     * @param startProcessReq 启动流程的请求对象，包含流程定义ID和动态参数等信息
     * @return 流程启动成功返回true，否则抛出异常
     * @throws BusinessException 当流程启动失败时抛出业务异常
     */
    @Override
    public Boolean startProcess(StartProcessReq startProcessReq) {
        try {
            // 参数校验：确保请求对象不为空
            if (startProcessReq == null) {
                log.error("启动流程失败，原因：请求对象不能为空");
                throw new IllegalArgumentException("启动流程失败，原因：请求对象不能为空");
            }

            String processDefinitionId = startProcessReq.getProcessDefinitionId();

            // 参数校验：确保流程定义ID不为空或空字符串
            if (StringUtils.isBlank(processDefinitionId)) {
                log.error("启动流程失败，原因：流程定义ID不能为空或空字符串，流程定义ID：{}", processDefinitionId);
                throw new IllegalArgumentException("启动流程失败，原因：流程定义ID不能为空或空字符串");
            }

            // 初始化 流程参数Map
            Map<String, Object> vars = new HashMap<>();

            // 获取流程动态参数
            List<StartProcessDynamicParametersReq> dynamicParameters = startProcessReq.getDynamicParameters();
            dynamicParameters.stream()
                    .filter(Objects::nonNull)
                    .forEach(dynamicParameter -> {
                        // 获取 动态参数名称
                        String dynamicVariableName = dynamicParameter.getDynamicVariableName();
                        if (!StringUtils.isBlank(dynamicVariableName)) {
                            // dynamicVariableName 去除首尾的 ${} 符号，使用正则表达式
                            String dynamicVariableKey = dynamicVariableName.replaceAll("^\\$\\{", "").replaceAll("\\}$", "");

                            // 获取动态参数值
                            List<String> dynamicVariableValue = dynamicParameter.getDynamicVariableValue();

                            // 设置参数
                            if (dynamicVariableValue.size() == 1) {
                                vars.put(dynamicVariableKey, dynamicVariableValue.get(0));
                            } else if (dynamicVariableValue.size() > 1) {
                                vars.put(dynamicVariableKey, dynamicVariableValue);
                            } else {
                                vars.put(dynamicVariableKey, null);
                            }
                        }
                    });

            // 获取当前用户信息
            UserInfo userInfo = userInfoUtils.getUserInfoFromAuthentication();
            // 设置流程发起人，获取当前用户ID
            if (userInfo != null) {
                identityService.setAuthenticatedUserId(String.valueOf(userInfo.getId()));
            }

            // 指定ID和动态参数启动流程
            runtimeService.startProcessInstanceById(processDefinitionId, vars);

            return true;
        } catch (IllegalArgumentException e) {
            log.error("启动流程失败，原因：参数错误", e);
            throw new BusinessException("启动流程失败，原因：参数错误", e);
        } catch (BusinessException e) {
            log.error("启动流程失败，原因：业务异常", e);
            throw new BusinessException("启动流程失败，原因：业务异常", e);
        } catch (Exception e) {
            log.error("启动流程失败，原因：未知异常", e);
            throw new BusinessException("启动流程失败，原因：未知异常", e);
        }
    }


    /**
     * 检查字符串是否以特殊模式包围
     * 该方法使用正则表达式判断字符串是否以"${"开始并以"}"结束
     * 这种模式常用于变量或表达式的包围，例如"${variable}"
     *
     * @param input 待检查的字符串
     * @return 如果字符串符合模式则返回true，否则返回false
     * 空字符串或null也会返回false
     */
    private boolean isSurroundedRegex(String input) {
        // 校验输入是否为非空字符串
        if (input == null || input.isEmpty()) {
            return false; // 明确空字符串或 null 的处理方式
        }

        // 使用非贪婪匹配优化正则表达式性能
        return input.matches("\\$\\{.*?\\}");
    }

    /**
     * 将给定的VO列表转换为SelectDataVO列表，用于在选择框中显示
     * 此方法主要处理的是将不同类型的VO对象转换为SelectDataVO对象，以便在界面上提供选择数据
     * 它特别关注于处理Oauth2BasicUserVO和SysRoleVO类型的对象，并根据条件判断是否将其包含在最终结果中
     *
     * @param voList 含有各种类型VO对象的列表，可以为空
     * @return 转换后的SelectDataVO列表，用于在选择框中显示
     */
    private List<SelectDataVO> convertToSelectDataVOList(List<?> voList) {
        return voList.stream().filter(Objects::nonNull).map(vo -> {
            // 检查vo对象是否为Oauth2BasicUserVO类型，并且符合条件
            if (vo instanceof Oauth2BasicUserVO oauth2BasicUserVO && isEligible(oauth2BasicUserVO)) {
                // 创建并返回一个SelectDataVO对象，用于显示用户信息
                return createSelectDataVO(oauth2BasicUserVO.getId().toString(), oauth2BasicUserVO.getName());
            } else if (vo instanceof SysRoleVO sysRoleVO && isEligible(sysRoleVO)) {
                // 创建并返回一个SelectDataVO对象，用于显示角色信息
                return createSelectDataVO(sysRoleVO.getId().toString(), sysRoleVO.getRoleName());
            }
            // 对于不符合条件的vo对象，返回null
            return null;
        }).filter(Objects::nonNull).toList();
    }

    /**
     * 检查用户是否符合资格条件
     * <p>
     * 此方法用于确定提供的用户信息是否满足特定条件主要检查用户对象是否非空，
     * 用户ID是否已分配，以及用户名是否已提供这些检查确保在后续的认证流程中，
     * 用户信息是有效且可以安全使用的
     *
     * @param oauth2BasicUserVO 用户信息对象，包含用户的基本信息
     * @return 如果用户符合所有资格条件，则返回true；否则返回false
     */
    private boolean isEligible(Oauth2BasicUserVO oauth2BasicUserVO) {
        // 确保用户信息对象不为空
        return oauth2BasicUserVO != null
                // 确保用户的ID已分配，即ID不为空
                && oauth2BasicUserVO.getId() != null
                // 确保用户名已提供，即用户名非空且不只包含空白字符
                && !StringUtils.isEmpty(oauth2BasicUserVO.getName());
    }

    /**
     * 判断一个系统角色对象是否符合资格条件
     * <p>
     * 本方法用于检查系统角色对象是否非空、角色ID是否非空以及角色名称是否非空
     * 这些检查确保了角色对象在后续操作中具有必要的信息，从而避免空指针异常等问题
     *
     * @param sysRoleVO 系统角色对象，包含角色相关信息
     * @return 如果角色对象非空、角色ID非空且角色名称非空，则返回true；否则返回false
     */
    private boolean isEligible(SysRoleVO sysRoleVO) {
        // 检查系统角色对象是否非空
        return sysRoleVO != null
                // 检查角色ID是否非空
                && sysRoleVO.getId() != null
                // 检查角色名称是否非空
                && !StringUtils.isEmpty(sysRoleVO.getRoleName());
    }

    /**
     * 创建SelectDataVO对象
     * 此方法用于初始化一个SelectDataVO实例，并设置其关键属性
     * 选择数据视图对象（SelectDataVO）是用于在界面上展示选择框的数据结构
     * 它需要关键（key）、标签（label）和值（value）三个属性，其中值通常与键相同
     *
     * @param key   选择项的唯一标识符，用于后端识别选择项
     * @param label 选择项在界面上显示的文本
     * @return 返回一个已初始化的SelectDataVO对象
     */
    private SelectDataVO createSelectDataVO(String key, String label) {
        SelectDataVO selectDataVO = new SelectDataVO();
        selectDataVO.setKey(key);
        selectDataVO.setLabel(label);
        selectDataVO.setValue(key);
        return selectDataVO;
    }

    /**
     * 处理用户任务的分配和动态参数设置
     *
     * @param userTask                用户任务对象，包含任务的分配信息
     * @param userSelectDataList      用户选择数据列表，用于动态表单的设置
     * @param roleSelectDataList      角色选择数据列表，用于动态表单的设置
     * @param dynamicParametersVOList 动态参数列表，用于存储生成的动态表单参数
     */
    private void processUserTask(UserTask userTask, List<SelectDataVO> userSelectDataList,
                                 List<SelectDataVO> roleSelectDataList,
                                 List<DynamicParametersVO> dynamicParametersVOList) {
        // 获取任务名称
        String taskName = userTask.getName();

        // 获取 assignee / candidateUsers / candidateGroups
        String assignee = userTask.getAssignee();
        List<String> candidateUsers = userTask.getCandidateUsers();
        List<String> candidateGroups = userTask.getCandidateGroups();

        // 这里虽然 候选人/候选组 的数量只能有一个，但是可以设置多个，所以这里需要判断一下,提示用户在设计的时候只能填一个参数
        if (candidateUsers.size() > 1 || candidateGroups.size() > 1) {
            log.error("获取动态参数不正确，请修改流程定义属性，办理人/候选组/候选用户的参数只能有一个");
            throw new BusinessException("获取动态参数不正确，请修改流程定义属性，办理人/候选组/候选用户的参数只能有一个");
        }

        // 处理 assignee 设置动态表单
        if (!StringUtils.isEmpty(assignee) && isSurroundedRegex(assignee)) {
            // 创建并添加动态参数对象到列表中
            DynamicParametersVO dynamicParametersVO = createDynamicParametersVO(taskName, assignee, 1,
                    userSelectDataList, "办理人");
            dynamicParametersVOList.add(dynamicParametersVO);
        }

        // 处理 candidateUsers 设置动态表单
        processCandidates(candidateUsers, taskName, userSelectDataList, dynamicParametersVOList, "候选人");

        // 处理 candidateGroups 设置动态表单
        processCandidates(candidateGroups, taskName, roleSelectDataList, dynamicParametersVOList, "候选组");
    }

    /**
     * 处理候选变量字符串列表，构建动态参数对象并添加到动态参数列表中
     *
     * @param candidates              候选变量字符串列表
     * @param taskName                任务名称，用于动态参数构建
     * @param selectDataList          选择数据列表，用于动态参数构建
     * @param dynamicParametersVOList 动态参数列表，处理后将添加新的动态参数对象
     * @param label                   动态参数的标签，用于动态参数构建
     */
    private void processCandidates(List<String> candidates, String taskName, List<SelectDataVO> selectDataList,
                                   List<DynamicParametersVO> dynamicParametersVOList, String label) {
        // 构建变量名称字符串，用于后续动态参数对象的创建
        StringBuilder variableNameBuilder = new StringBuilder();
        // 初始化选择限制计数器，用于记录有效候选变量的数量
        int selectLimit = 0;

        // 遍历候选变量列表
        for (String candidate : candidates) {
            // 检查候选变量是否非空且符合正则表达式包围的条件
            if (!StringUtils.isEmpty(candidate) && isSurroundedRegex(candidate)) {
                // 如果变量名称构建器非空，追加逗号分隔符
                if (!variableNameBuilder.isEmpty()) {
                    variableNameBuilder.append(",");
                }
                // 将符合条件的候选变量追加到变量名称构建器中
                variableNameBuilder.append(candidate);
                // 增加选择限制计数
                selectLimit++;
            }
        }

        // 如果存在有效候选变量，创建动态参数对象并添加到列表中,并且不限制多选个数
        if (selectLimit > 0) {
            DynamicParametersVO dynamicParametersVO = createDynamicParametersVO(taskName,
                    variableNameBuilder.toString(), 0, selectDataList, label);
            dynamicParametersVOList.add(dynamicParametersVO);
        }
    }

    /**
     * 创建DynamicParametersVO对象的方法
     * 该方法用于根据传入的任务名称、动态变量名称、选择限制和选择数据列表来构建一个DynamicParametersVO对象
     *
     * @param taskName            任务名称，用于标识特定的任务
     * @param dynamicVariableName 动态变量名称，用于标识动态参数
     * @param selectLimit         选择限制，定义用户可以选择的项数限制
     * @param selectDataList      选择数据列表，包含用户可以选择的数据项
     * @param label               动态参数的标签，用于描述动态参数
     * @return 返回构建好的DynamicParametersVO对象
     */
    private DynamicParametersVO createDynamicParametersVO(String taskName, String dynamicVariableName,
                                                          int selectLimit, List<SelectDataVO> selectDataList,
                                                          String label) {
        // 创建DynamicParametersVO对象实例
        DynamicParametersVO dynamicParametersVO = new DynamicParametersVO();

        // 设置表单名称，由任务名称和动态变量名称组合而成，用于唯一标识该动态参数
        dynamicParametersVO.setFormName(taskName + "-" + label);

        // 设置任务名称
        dynamicParametersVO.setTaskName(taskName);

        // 设置动态变量名称
        dynamicParametersVO.setDynamicVariableName(dynamicVariableName);

        // 设置选择限制
        dynamicParametersVO.setSelectLimit(selectLimit);

        // 设置选择数据列表
        dynamicParametersVO.setSelectData(selectDataList);

        // 返回构建好的DynamicParametersVO对象
        return dynamicParametersVO;
    }


}
